/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 31.05.2007
 *
 * Source: $HeadURL$
 * Last changed: $LastChangedDate$
 * 
 * the unrar licence applies to all junrar source and binary distributions 
 * you are not allowed to use this source to re-create the RAR compression algorithm
 * 
 * Here some html entities which can be used for escaping javadoc tags:
 * "&":  "&#038;" or "&amp;"
 * "<":  "&#060;" or "&lt;"
 * ">":  "&#062;" or "&gt;"
 * "@":  "&#064;" 
 */
package com.github.junrar.unpack.vm;

import java.util.List;
import java.util.Vector;

import com.github.junrar.crc.RarCRC;
import com.github.junrar.io.Raw;

/**
 * DOCUMENT ME
 * 
 * @author $LastChangedBy$
 * @version $LastChangedRevision$
 */
public class RarVM extends BitInput
 {

  public static final int VM_MEMSIZE = 0x40000;

  public static final int VM_MEMMASK = (VM_MEMSIZE - 1);

  public static final int VM_GLOBALMEMADDR = 0x3C000;

  public static final int VM_GLOBALMEMSIZE = 0x2000;

  public static final int VM_FIXEDGLOBALSIZE = 64;

  private static final int regCount = 8;

  private static final long UINT_MASK = 0xffffFFFF;// ((long)2*(long)Integer.MAX_VALUE);

  private byte[] mem;

  private int[] R = new int[regCount];

  private int flags;

  private int maxOpCount = 25000000;

  private int codeSize;

  private int IP;

  public RarVM()
   {
    mem = null;
   }

  public void init()
   {
    if (mem == null)
     {
      mem = new byte[VM_MEMSIZE + 4];
     }
   }

  private boolean isVMMem(byte[] mem)
   {
    return this.mem == mem;
   }

  private int getValue(boolean byteMode, byte[] mem, int offset)
   {
    if (byteMode)
     {
      if (isVMMem(mem))
       {
        return (mem[offset]);
       }
      else
       {
        return (mem[offset] & 0xff);
       }
     }
    else
     {
      if (isVMMem(mem))
       {
        // little
        return Raw.readIntLittleEndian(mem, offset);
       }
      else
       // big endian
       return Raw.readIntBigEndian(mem, offset);
     }
   }

  private void setValue(boolean byteMode, byte[] mem, int offset, int value)
   {
    if (byteMode)
     {
      if (isVMMem(mem))
       {
        mem[offset] = (byte) value;
       }
      else
       {
        mem[offset] = (byte) ((mem[offset] & 0x00) | (byte) (value & 0xff));
       }
     }
    else
     {
      if (isVMMem(mem))
       {
        Raw.writeIntLittleEndian(mem, offset, value);
        // mem[offset + 0] = (byte) value;
        // mem[offset + 1] = (byte) (value >>> 8);
        // mem[offset + 2] = (byte) (value >>> 16);
        // mem[offset + 3] = (byte) (value >>> 24);
       }
      else
       {
        Raw.writeIntBigEndian(mem, offset, value);
        // mem[offset + 3] = (byte) value;
        // mem[offset + 2] = (byte) (value >>> 8);
        // mem[offset + 1] = (byte) (value >>> 16);
        // mem[offset + 0] = (byte) (value >>> 24);
       }

     }
    // #define SET_VALUE(ByteMode,Addr,Value) SetValue(ByteMode,(uint
    // *)Addr,Value)
   }

  public void setLowEndianValue(byte[] mem, int offset, int value)
   {
    Raw.writeIntLittleEndian(mem, offset, value);
    // mem[offset + 0] = (byte) (value&0xff);
    // mem[offset + 1] = (byte) ((value >>> 8)&0xff);
    // mem[offset + 2] = (byte) ((value >>> 16)&0xff);
    // mem[offset + 3] = (byte) ((value >>> 24)&0xff);
   }

  public void setLowEndianValue(Vector<Byte> mem, int offset, int value)
   {
    mem.set(offset + 0, Byte.valueOf((byte) (value & 0xff)));
    mem.set(offset + 1, Byte.valueOf((byte) ((value >>> 8) & 0xff)));
    mem.set(offset + 2, Byte.valueOf((byte) ((value >>> 16) & 0xff)));
    mem.set(offset + 3, Byte.valueOf((byte) ((value >>> 24) & 0xff)));
   }

  private int getOperand(VMPreparedOperand cmdOp)
   {
    int ret = 0;
    if (cmdOp.getType() == VMOpType.VM_OPREGMEM)
     {
      int pos = (cmdOp.getOffset() + cmdOp.getBase()) & VM_MEMMASK;
      ret = Raw.readIntLittleEndian(mem, pos);
     }
    else
     {
      int pos = cmdOp.getOffset();
      ret = Raw.readIntLittleEndian(mem, pos);
     }
    return ret;
   }

  public void execute(VMPreparedProgram prg)
   {
    for (int i = 0; i < prg.getInitR().length; i++) // memcpy(R,Prg->InitR,sizeof(Prg->InitR));
     {
      R[i] = prg.getInitR()[i];
     }

    long globalSize = Math.min(prg.getGlobalData().size(), VM_GLOBALMEMSIZE) & 0xffFFffFF;
    if (globalSize != 0)
     {
      for (int i = 0; i < globalSize; i++) // memcpy(Mem+VM_GLOBALMEMADDR,&Prg->GlobalData[0],GlobalSize);
       {
        mem[VM_GLOBALMEMADDR + i] = prg.getGlobalData().get(i);
       }

     }
    long staticSize = Math.min(prg.getStaticData().size(), VM_GLOBALMEMSIZE - globalSize) & 0xffFFffFF;
    if (staticSize != 0)
     {
      for (int i = 0; i < staticSize; i++) // memcpy(Mem+VM_GLOBALMEMADDR+GlobalSize,&Prg->StaticData[0],StaticSize);
       {
        mem[VM_GLOBALMEMADDR + (int) globalSize + i] = prg.getStaticData().get(i);
       }

     }
    R[7] = VM_MEMSIZE;
    flags = 0;

    List<VMPreparedCommand> preparedCode = prg.getAltCmd().size() != 0 ? prg.getAltCmd() : prg.getCmd();

    if (!ExecuteCode(preparedCode, prg.getCmdCount()))
     {
      preparedCode.get(0).setOpCode(VMCommands.VM_RET);
     }
    int newBlockPos = getValue(false, mem, VM_GLOBALMEMADDR + 0x20) & VM_MEMMASK;
    int newBlockSize = getValue(false, mem, VM_GLOBALMEMADDR + 0x1c) & VM_MEMMASK;
    if ((newBlockPos + newBlockSize) >= VM_MEMSIZE)
     {
      newBlockPos = 0;
      newBlockSize = 0;
     }

    prg.setFilteredDataOffset(newBlockPos);
    prg.setFilteredDataSize(newBlockSize);

    prg.getGlobalData().clear();

    int dataSize = Math.min(getValue(false, mem, VM_GLOBALMEMADDR + 0x30), VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE);
    if (dataSize != 0)
     {
      prg.getGlobalData().setSize(dataSize + VM_FIXEDGLOBALSIZE);
      // ->GlobalData.Add(dataSize+VM_FIXEDGLOBALSIZE);

      for (int i = 0; i < dataSize + VM_FIXEDGLOBALSIZE; i++) // memcpy(&Prg->GlobalData[0],&Mem[VM_GLOBALMEMADDR],DataSize+VM_FIXEDGLOBALSIZE);
       {
        prg.getGlobalData().set(i, mem[VM_GLOBALMEMADDR + i]);
       }
     }
   }

  public byte[] getMem()
   {
    return mem;
   }

  private boolean setIP(int ip)
   {
    if ((ip) >= codeSize)
     {
      return (true);
     }

    if (--maxOpCount <= 0)
     {
      return (false);
     }

    IP = ip;
    return true;
   }

  private boolean ExecuteCode(List<VMPreparedCommand> preparedCode, int cmdCount)
   {

    maxOpCount = 25000000;
    this.codeSize = cmdCount;
    this.IP = 0;

    while (true)
     {
      VMPreparedCommand cmd = preparedCode.get(IP);
      int op1 = getOperand(cmd.getOp1());
      int op2 = getOperand(cmd.getOp2());
      switch (cmd.getOpCode())
       {
       case VM_MOV:
        setValue(cmd.isByteMode(), mem, op1, getValue(cmd.isByteMode(), mem, op2)); // SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2));
        break;
       case VM_MOVB:
        setValue(true, mem, op1, getValue(true, mem, op2));
        break;
       case VM_MOVD:
        setValue(false, mem, op1, getValue(false, mem, op2));
        break;

       case VM_CMP:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int result = value1 - getValue(cmd.isByteMode(), mem, op2);

         if (result == 0)
          {
           flags = VMFlags.VM_FZ.getFlag();
          }
         else
          {
           flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS.getFlag());
          }
        }
        break;

       case VM_CMPB:
        {
         int value1 = getValue(true, mem, op1);
         int result = value1 - getValue(true, mem, op2);
         if (result == 0)
          {
           flags = VMFlags.VM_FZ.getFlag();
          }
         else
          {
           flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS.getFlag());
          }
        }
        break;
       case VM_CMPD:
        {
         int value1 = getValue(false, mem, op1);
         int result = value1 - getValue(false, mem, op2);
         if (result == 0)
          {
           flags = VMFlags.VM_FZ.getFlag();
          }
         else
          {
           flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS.getFlag());
          }
        }
        break;

       case VM_ADD:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int result = (int) ((((long) value1 + (long) getValue(cmd.isByteMode(), mem, op2))) & 0xffffffff);
         if (cmd.isByteMode())
          {
           result &= 0xff;
           flags = (result < value1) ? 1 : 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
                                                           : ((result & 0x80) != 0) ? VMFlags.VM_FS.getFlag() : 0);
           // Flags=(Result<Value1)|(Result==0 ? VM_FZ:((Result&0x80) ?
           // VM_FS:0));
          }
         else
          flags = (result < value1) ? 1 : 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
                                                          : (result & VMFlags.VM_FS.getFlag()));
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;

       case VM_ADDB:
        setValue(true, mem, op1,
                 (int) ((long) getValue(true, mem, op1) & 0xFFffFFff + (long) getValue(true, mem, op2) & 0xFFffFFff));
        break;
       case VM_ADDD:
        setValue(false, mem, op1,
                 (int) ((long) getValue(false, mem, op1) & 0xFFffFFff + (long) getValue(false, mem, op2) & 0xFFffFFff));
        break;

       case VM_SUB:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int result = (int) ((long) value1 & 0xffFFffFF - (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff);
         flags = (result == 0) ? VMFlags.VM_FZ.getFlag() : (result > value1) ? 1
                                                                            : 0 | (result & VMFlags.VM_FS.getFlag());
         setValue(cmd.isByteMode(), mem, op1, result);// (Cmd->ByteMode,Op1,Result);
        }
        break;

       case VM_SUBB:
        setValue(true, mem, op1,
                 (int) ((long) getValue(true, mem, op1) & 0xFFffFFff - (long) getValue(true, mem, op2) & 0xFFffFFff));
        break;
       case VM_SUBD:
        setValue(false, mem, op1,
                 (int) ((long) getValue(false, mem, op1) & 0xFFffFFff - (long) getValue(false, mem, op2) & 0xFFffFFff));
        break;

       case VM_JZ:
        if ((flags & VMFlags.VM_FZ.getFlag()) != 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JNZ:
        if ((flags & VMFlags.VM_FZ.getFlag()) == 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_INC:
        {
         int result = (int) ((long) getValue(cmd.isByteMode(), mem, op1) & 0xFFffFFff + 1);
         if (cmd.isByteMode())
          {
           result &= 0xff;
          }

         setValue(cmd.isByteMode(), mem, op1, result);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
        }
        break;

       case VM_INCB:
        setValue(true, mem, op1, (int) ((long) getValue(true, mem, op1) & 0xFFffFFff + 1));
        break;
       case VM_INCD:
        setValue(false, mem, op1, (int) ((long) getValue(false, mem, op1) & 0xFFffFFff + 1));
        break;

       case VM_DEC:
        {
         int result = (int) ((long) getValue(cmd.isByteMode(), mem, op1) & 0xFFffFFff - 1);
         setValue(cmd.isByteMode(), mem, op1, result);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
        }
        break;

       case VM_DECB:
        setValue(true, mem, op1, (int) ((long) getValue(true, mem, op1) & 0xFFffFFff - 1));
        break;
       case VM_DECD:
        setValue(false, mem, op1, (int) ((long) getValue(false, mem, op1) & 0xFFffFFff - 1));
        break;

       case VM_JMP:
        setIP(getValue(false, mem, op1));
        continue;
       case VM_XOR:
        {
         int result = getValue(cmd.isByteMode(), mem, op1) ^ getValue(cmd.isByteMode(), mem, op2);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_AND:
        {
         int result = getValue(cmd.isByteMode(), mem, op1) & getValue(cmd.isByteMode(), mem, op2);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_OR:
        {
         int result = getValue(cmd.isByteMode(), mem, op1) | getValue(cmd.isByteMode(), mem, op2);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_TEST:
        {
         int result = getValue(cmd.isByteMode(), mem, op1) & getValue(cmd.isByteMode(), mem, op2);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result & VMFlags.VM_FS.getFlag();
        }
        break;
       case VM_JS:
        if ((flags & VMFlags.VM_FS.getFlag()) != 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JNS:
        if ((flags & VMFlags.VM_FS.getFlag()) == 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JB:
        if ((flags & VMFlags.VM_FC.getFlag()) != 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JBE:
        if ((flags & (VMFlags.VM_FC.getFlag() | VMFlags.VM_FZ.getFlag())) != 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JA:
        if ((flags & (VMFlags.VM_FC.getFlag() | VMFlags.VM_FZ.getFlag())) == 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_JAE:
        if ((flags & VMFlags.VM_FC.getFlag()) == 0)
         {
          setIP(getValue(false, mem, op1));
          continue;
         }
        break;
       case VM_PUSH:
        R[7] -= 4;
        setValue(false, mem, R[7] & VM_MEMMASK, getValue(false, mem, op1));
        break;
       case VM_POP:
        setValue(false, mem, op1, getValue(false, mem, R[7] & VM_MEMMASK));
        R[7] += 4;
        break;
       case VM_CALL:
        R[7] -= 4;
        setValue(false, mem, R[7] & VM_MEMMASK, IP + 1);
        setIP(getValue(false, mem, op1));
        continue;
       case VM_NOT:
        setValue(cmd.isByteMode(), mem, op1, ~getValue(cmd.isByteMode(), mem, op1));
        break;
       case VM_SHL:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int value2 = getValue(cmd.isByteMode(), mem, op2);
         int result = value1 << value2;
         flags = (result == 0 ? VMFlags.VM_FZ.getFlag() : (result & VMFlags.VM_FS.getFlag()))
                 | (((value1 << (value2 - 1)) & 0x80000000) != 0 ? VMFlags.VM_FC.getFlag() : 0);
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_SHR:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int value2 = getValue(cmd.isByteMode(), mem, op2);
         int result = value1 >>> value2;
         flags = (result == 0 ? VMFlags.VM_FZ.getFlag() : (result & VMFlags.VM_FS.getFlag()))
                 | ((value1 >>> (value2 - 1)) & VMFlags.VM_FC.getFlag());
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_SAR:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int value2 = getValue(cmd.isByteMode(), mem, op2);
         int result = ((int) value1) >> value2;
         flags = (result == 0 ? VMFlags.VM_FZ.getFlag() : (result & VMFlags.VM_FS.getFlag()))
                 | ((value1 >> (value2 - 1)) & VMFlags.VM_FC.getFlag());
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_NEG:
        {
         int result = -getValue(cmd.isByteMode(), mem, op1);
         flags = result == 0 ? VMFlags.VM_FZ.getFlag() : VMFlags.VM_FC.getFlag() | (result & VMFlags.VM_FS.getFlag());
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;

       case VM_NEGB:
        setValue(true, mem, op1, -getValue(true, mem, op1));
        break;
       case VM_NEGD:
        setValue(false, mem, op1, -getValue(false, mem, op1));
        break;
       case VM_PUSHA:
        {
         for (int i = 0, SP = R[7] - 4; i < regCount; i++, SP -= 4)
          {
           setValue(false, mem, SP & VM_MEMMASK, R[i]);
          }
         R[7] -= regCount * 4;
        }
        break;
       case VM_POPA:
        {
         for (int i = 0, SP = R[7]; i < regCount; i++, SP += 4)
          R[7 - i] = getValue(false, mem, SP & VM_MEMMASK);
        }
        break;
       case VM_PUSHF:
        R[7] -= 4;
        setValue(false, mem, R[7] & VM_MEMMASK, flags);
        break;
       case VM_POPF:
        flags = getValue(false, mem, R[7] & VM_MEMMASK);
        R[7] += 4;
        break;
       case VM_MOVZX:
        setValue(false, mem, op1, getValue(true, mem, op2));
        break;
       case VM_MOVSX:
        setValue(false, mem, op1, (byte) getValue(true, mem, op2));
        break;
       case VM_XCHG:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         setValue(cmd.isByteMode(), mem, op1, getValue(cmd.isByteMode(), mem, op2));
         setValue(cmd.isByteMode(), mem, op2, value1);
        }
        break;
       case VM_MUL:
        {
         int result = (int) (((long) getValue(cmd.isByteMode(), mem, op1) & 0xFFffFFff
                              * (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff) & 0xFFffFFff);
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_DIV:
        {
         int divider = getValue(cmd.isByteMode(), mem, op2);
         if (divider != 0)
          {
           int result = getValue(cmd.isByteMode(), mem, op1) / divider;
           setValue(cmd.isByteMode(), mem, op1, result);
          }
        }
        break;
       case VM_ADC:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int FC = (flags & VMFlags.VM_FC.getFlag());
         int result = (int) ((long) value1 & 0xFFffFFff + (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff
                             + (long) FC & 0xFFffFFff);
         if (cmd.isByteMode())
          {
           result &= 0xff;
          }

         flags = (result < value1 || result == value1 && FC != 0) ? 1
                                                                 : 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
                                                                                   : (result & VMFlags.VM_FS.getFlag()));
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;
       case VM_SBB:
        {
         int value1 = getValue(cmd.isByteMode(), mem, op1);
         int FC = (flags & VMFlags.VM_FC.getFlag());
         int result = (int) ((long) value1 & 0xFFffFFff - (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff
                             - (long) FC & 0xFFffFFff);
         if (cmd.isByteMode())
          {
           result &= 0xff;
          }
         flags = (result > value1 || result == value1 && FC != 0) ? 1
                                                                 : 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
                                                                                   : (result & VMFlags.VM_FS.getFlag()));
         setValue(cmd.isByteMode(), mem, op1, result);
        }
        break;

       case VM_RET:
        if (R[7] >= VM_MEMSIZE)
         {
          return (true);
         }
        setIP(getValue(false, mem, R[7] & VM_MEMMASK));
        R[7] += 4;
        continue;

       case VM_STANDARD:
        ExecuteStandardFilter(VMStandardFilters.findFilter(cmd.getOp1().getData()));
        break;
       case VM_PRINT:
        break;
       }
      IP++;
      --maxOpCount;
     }
   }

  public void prepare(byte[] code, int codeSize, VMPreparedProgram prg)
   {
    InitBitInput();
    int cpLength = Math.min(MAX_SIZE, codeSize);
    for (int i = 0; i < cpLength; i++) // memcpy(inBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
     {
      inBuf[i] |= code[i];
     }

    byte xorSum = 0;
    for (int i = 1; i < codeSize; i++)
     {
      xorSum ^= code[i];
     }

    faddbits(8);

    prg.setCmdCount(0);
    if (xorSum == code[0])
     {
      VMStandardFilters filterType = IsStandardFilter(code, codeSize);
      if (filterType != VMStandardFilters.VMSF_NONE)
       {

        VMPreparedCommand curCmd = new VMPreparedCommand();
        curCmd.setOpCode(VMCommands.VM_STANDARD);
        curCmd.getOp1().setData(filterType.getFilter());
        curCmd.getOp1().setType(VMOpType.VM_OPNONE);
        curCmd.getOp2().setType(VMOpType.VM_OPNONE);
        codeSize = 0;
        prg.getCmd().add(curCmd);
        prg.setCmdCount(prg.getCmdCount() + 1);
        // TODO
        // curCmd->Op1.Data=FilterType;
        // >>>>>> CurCmd->Op1.Addr=&CurCmd->Op1.Data; <<<<<<<<<< not set
        // do i need to ?
        // >>>>>> CurCmd->Op2.Addr=&CurCmd->Op2.Data; <<<<<<<<<< "
        // CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE;
        // CodeSize=0;
       }
      int dataFlag = fgetbits();
      faddbits(1);

      // Read static data contained in DB operators. This data cannot be
      // changed,
      // it is a part of VM code, not a filter parameter.

      if ((dataFlag & 0x8000) != 0)
       {
        long dataSize = (long) ((long) ReadData(this) & 0xffFFffFF + 1);
        for (int i = 0; inAddr < codeSize && i < dataSize; i++)
         {
          prg.getStaticData().add(Byte.valueOf((byte) (fgetbits() >> 8)));
          faddbits(8);
         }
       }

      while (inAddr < codeSize)
       {
        VMPreparedCommand curCmd = new VMPreparedCommand();
        int data = fgetbits();
        if ((data & 0x8000) == 0)
         {
          curCmd.setOpCode(VMCommands.findVMCommand((data >> 12)));
          faddbits(4);
         }
        else
         {
          curCmd.setOpCode(VMCommands.findVMCommand((data >> 10) - 24));
          faddbits(6);
         }
        if ((VMCmdFlags.VM_CmdFlags[curCmd.getOpCode().getVMCommand()] & VMCmdFlags.VMCF_BYTEMODE) != 0)
         {
          curCmd.setByteMode((fgetbits() >> 15) == 1 ? true : false);
          faddbits(1);
         }
        else
         {
          curCmd.setByteMode(false);
         }
        curCmd.getOp1().setType(VMOpType.VM_OPNONE);
        curCmd.getOp2().setType(VMOpType.VM_OPNONE);

        int opNum = (VMCmdFlags.VM_CmdFlags[curCmd.getOpCode().getVMCommand()] & VMCmdFlags.VMCF_OPMASK);
        // TODO >>> CurCmd->Op1.Addr=CurCmd->Op2.Addr=NULL; <<<???
        if (opNum > 0)
         {
          decodeArg(curCmd.getOp1(), curCmd.isByteMode());
          if (opNum == 2)
           decodeArg(curCmd.getOp2(), curCmd.isByteMode());
          else
           {
            if (curCmd.getOp1().getType() == VMOpType.VM_OPINT
                && (VMCmdFlags.VM_CmdFlags[curCmd.getOpCode().getVMCommand()] & (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC)) != 0)
             {
              int distance = curCmd.getOp1().getData();
              if (distance >= 256)
               distance -= 256;
              else
               {
                if (distance >= 136)
                 {
                  distance -= 264;
                 }
                else
                 {
                  if (distance >= 16)
                   {
                    distance -= 8;
                   }
                  else
                   {
                    if (distance >= 8)
                     {
                      distance -= 16;
                     }
                   }
                 }
                distance += prg.getCmdCount();
               }
              curCmd.getOp1().setData(distance);
             }
           }
         }
        prg.setCmdCount(prg.getCmdCount() + 1);
        prg.getCmd().add(curCmd);
       }
     }
    VMPreparedCommand curCmd = new VMPreparedCommand();
    curCmd.setOpCode(VMCommands.VM_RET);
    // TODO CurCmd->Op1.Addr=&CurCmd->Op1.Data;
    // CurCmd->Op2.Addr=&CurCmd->Op2.Data;
    curCmd.getOp1().setType(VMOpType.VM_OPNONE);
    curCmd.getOp2().setType(VMOpType.VM_OPNONE);

    // for (int i=0;i<prg.getCmdCount();i++)
    // {
    // VM_PreparedCommand *Cmd=&Prg->Cmd[I];
    // if (Cmd->Op1.Addr==NULL)
    // Cmd->Op1.Addr=&Cmd->Op1.Data;
    // if (Cmd->Op2.Addr==NULL)
    // Cmd->Op2.Addr=&Cmd->Op2.Data;
    // }

    prg.getCmd().add(curCmd);
    prg.setCmdCount(prg.getCmdCount() + 1);
    // #ifdef VM_OPTIMIZE
    if (codeSize != 0)
     {
      optimize(prg);
     }
   }

  private void decodeArg(VMPreparedOperand op, boolean byteMode)
   {
    int data = fgetbits();
    if ((data & 0x8000) != 0)
     {
      op.setType(VMOpType.VM_OPREG);
      op.setData((data >> 12) & 7);
      op.setOffset(op.getData());
      faddbits(4);
     }
    else
     {
      if ((data & 0xc000) == 0)
       {
        op.setType(VMOpType.VM_OPINT);
        if (byteMode)
         {
          op.setData((data >> 6) & 0xff);
          faddbits(10);
         }
        else
         {
          faddbits(2);
          op.setData(ReadData(this));
         }
       }
      else
       {
        op.setType(VMOpType.VM_OPREGMEM);
        if ((data & 0x2000) == 0)
         {
          op.setData((data >> 10) & 7);
          op.setOffset(op.getData());
          op.setBase(0);
          faddbits(6);
         }
        else
         {
          if ((data & 0x1000) == 0)
           {
            op.setData((data >> 9) & 7);
            op.setOffset(op.getData());
            faddbits(7);
           }
          else
           {
            op.setData(0);
            faddbits(4);
           }
          op.setBase(ReadData(this));
         }
       }
     }

   }

  private void optimize(VMPreparedProgram prg)
   {
    List<VMPreparedCommand> commands = prg.getCmd();

    for (VMPreparedCommand cmd : commands)
     {
      switch (cmd.getOpCode())
       {
       case VM_MOV:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_MOVB : VMCommands.VM_MOVD);
        continue;
       case VM_CMP:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_CMPB : VMCommands.VM_CMPD);
        continue;
       }
      if ((VMCmdFlags.VM_CmdFlags[cmd.getOpCode().getVMCommand()] & VMCmdFlags.VMCF_CHFLAGS) == 0)
       {
        continue;
       }
      boolean flagsRequired = false;

      for (int i = commands.indexOf(cmd) + 1; i < commands.size(); i++)
       {
        int flags = VMCmdFlags.VM_CmdFlags[commands.get(i).getOpCode().getVMCommand()];
        if ((flags & (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC | VMCmdFlags.VMCF_USEFLAGS)) != 0)
         {
          flagsRequired = true;
          break;
         }
        if ((flags & VMCmdFlags.VMCF_CHFLAGS) != 0)
         {
          break;
         }
       }
      if (flagsRequired)
       {
        continue;
       }
      switch (cmd.getOpCode())
       {
       case VM_ADD:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_ADDB : VMCommands.VM_ADDD);
        continue;
       case VM_SUB:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_SUBB : VMCommands.VM_SUBD);
        continue;
       case VM_INC:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_INCB : VMCommands.VM_INCD);
        continue;
       case VM_DEC:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_DECB : VMCommands.VM_DECD);
        continue;
       case VM_NEG:
        cmd.setOpCode(cmd.isByteMode() ? VMCommands.VM_NEGB : VMCommands.VM_NEGD);
        continue;
       }
     }

   }

  public static int ReadData(BitInput rarVM)
   {
    int data = rarVM.fgetbits();
    switch (data & 0xc000)
     {
     case 0:
      rarVM.faddbits(6);
      return ((data >> 10) & 0xf);
     case 0x4000:
      if ((data & 0x3c00) == 0)
       {
        data = 0xffffff00 | ((data >> 2) & 0xff);
        rarVM.faddbits(14);
       }
      else
       {
        data = (data >> 6) & 0xff;
        rarVM.faddbits(10);
       }
      return (data);
     case 0x8000:
      rarVM.faddbits(2);
      data = rarVM.fgetbits();
      rarVM.faddbits(16);
      return (data);
     default:
      rarVM.faddbits(2);
      data = (rarVM.fgetbits() << 16);
      rarVM.faddbits(16);
      data |= rarVM.fgetbits();
      rarVM.faddbits(16);
      return (data);
     }
   }

  private VMStandardFilters IsStandardFilter(byte[] code, int codeSize)
   {
    VMStandardFilterSignature stdList[] = {
                                           new VMStandardFilterSignature(53, 0xad576887, VMStandardFilters.VMSF_E8),
                                           new VMStandardFilterSignature(57, 0x3cd7e57e, VMStandardFilters.VMSF_E8E9),
                                           new VMStandardFilterSignature(120, 0x3769893f,
                                                                         VMStandardFilters.VMSF_ITANIUM),
                                           new VMStandardFilterSignature(29, 0x0e06077d, VMStandardFilters.VMSF_DELTA),
                                           new VMStandardFilterSignature(149, 0x1c2c5dc8, VMStandardFilters.VMSF_RGB),
                                           new VMStandardFilterSignature(216, 0xbc85e701, VMStandardFilters.VMSF_AUDIO),
                                           new VMStandardFilterSignature(40, 0x46b9c560, VMStandardFilters.VMSF_UPCASE)};
    int CodeCRC = RarCRC.checkCrc(0xffffffff, code, 0, code.length) ^ 0xffffffff;
    for (int i = 0; i < stdList.length; i++)
     {
      if (stdList[i].getCRC() == CodeCRC && stdList[i].getLength() == code.length)
       {
        return (stdList[i].getType());
       }

     }
    return (VMStandardFilters.VMSF_NONE);
   }

  private void ExecuteStandardFilter(VMStandardFilters filterType)
   {
    switch (filterType)
     {
     case VMSF_E8:
     case VMSF_E8E9:
      {
       int dataSize = R[4];
       long fileOffset = R[6] & 0xFFffFFff;

       if (dataSize >= VM_GLOBALMEMADDR)
        {
         break;
        }
       int fileSize = 0x1000000;
       byte cmpByte2 = (byte) ((filterType == VMStandardFilters.VMSF_E8E9) ? 0xe9 : 0xe8);
       for (int curPos = 0; curPos < dataSize - 4;)
        {
         byte curByte = mem[curPos++];
         if (curByte == 0xe8 || curByte == cmpByte2)
          {
           // #ifdef PRESENT_INT32
           // sint32 Offset=CurPos+FileOffset;
           // sint32 Addr=GET_VALUE(false,Data);
           // if (Addr<0)
           // {
           // if (Addr+Offset>=0)
           // SET_VALUE(false,Data,Addr+FileSize);
           // }
           // else
           // if (Addr<FileSize)
           // SET_VALUE(false,Data,Addr-Offset);
           // #else
           long offset = curPos + fileOffset;
           long Addr = getValue(false, mem, curPos);
           if ((Addr & 0x80000000) != 0)
            {
             if (((Addr + offset) & 0x80000000) == 0)
              setValue(false, mem, curPos, (int) Addr + fileSize);
            }
           else
            {
             if (((Addr - fileSize) & 0x80000000) != 0)
              {
               setValue(false, mem, curPos, (int) (Addr - offset));
              }
            }
           // #endif
           curPos += 4;
          }
        }
      }
      break;
     case VMSF_ITANIUM:
      {

       int dataSize = R[4];
       long fileOffset = R[6] & 0xFFffFFff;

       if (dataSize >= VM_GLOBALMEMADDR)
        {
         break;
        }
       int curPos = 0;
       final byte Masks[] = {4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0};
       fileOffset >>>= 4;

       while (curPos < dataSize - 21)
        {
         int Byte = (mem[curPos] & 0x1f) - 0x10;
         if (Byte >= 0)
          {

           byte cmdMask = Masks[Byte];
           if (cmdMask != 0)
            for (int i = 0; i <= 2; i++)
             if ((cmdMask & (1 << i)) != 0)
              {
               int startPos = i * 41 + 5;
               int opType = filterItanium_GetBits(curPos, startPos + 37, 4);
               if (opType == 5)
                {
                 int offset = filterItanium_GetBits(curPos, startPos + 13, 20);
                 filterItanium_SetBits(curPos, (int) (offset - fileOffset) & 0xfffff, startPos + 13, 20);
                }
              }
          }
         curPos += 16;
         fileOffset++;
        }
      }
      break;
     case VMSF_DELTA:
      {
       int dataSize = R[4] & 0xFFffFFff;
       int channels = R[0] & 0xFFffFFff;
       int srcPos = 0;
       int border = (dataSize * 2) & 0xFFffFFff;
       setValue(false, mem, VM_GLOBALMEMADDR + 0x20, (int) dataSize);
       if (dataSize >= VM_GLOBALMEMADDR / 2)
        {
         break;
        }
       // bytes from same channels are grouped to continual data blocks,
       // so we need to place them back to their interleaving positions

       for (int curChannel = 0; curChannel < channels; curChannel++)
        {
         byte PrevByte = 0;
         for (int destPos = dataSize + curChannel; destPos < border; destPos += channels)
          {
           mem[destPos] = (PrevByte -= mem[srcPos++]);
          }

        }
      }
      break;
     case VMSF_RGB:
      {
       // byte *SrcData=Mem,*DestData=SrcData+DataSize;
       int dataSize = R[4], width = R[0] - 3, posR = R[1];
       int channels = 3;
       int srcPos = 0;
       int destDataPos = dataSize;
       setValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
       if (dataSize >= VM_GLOBALMEMADDR / 2 || posR < 0)
        {
         break;
        }
       for (int curChannel = 0; curChannel < channels; curChannel++)
        {
         long prevByte = 0;

         for (int i = curChannel; i < dataSize; i += channels)
          {
           long predicted;
           int upperPos = i - width;
           if (upperPos >= 3)
            {
             int upperDataPos = destDataPos + upperPos;
             int upperByte = mem[(int) upperDataPos] & 0xff;
             int upperLeftByte = mem[upperDataPos - 3] & 0xff;
             predicted = prevByte + upperByte - upperLeftByte;
             int pa = Math.abs((int) (predicted - prevByte));
             int pb = Math.abs((int) (predicted - upperByte));
             int pc = Math.abs((int) (predicted - upperLeftByte));
             if (pa <= pb && pa <= pc)
              {
               predicted = prevByte;
              }
             else
              {
               if (pb <= pc)
                {
                 predicted = upperByte;
                }
               else
                {
                 predicted = upperLeftByte;
                }
              }
            }
           else
            {
             predicted = prevByte;
            }

           prevByte = (predicted - mem[srcPos++] & 0xff) & 0xff;
           mem[destDataPos + i] = (byte) (prevByte & 0xff);

          }
        }
       for (int i = posR, border = dataSize - 2; i < border; i += 3)
        {
         byte G = mem[destDataPos + i + 1];
         mem[destDataPos + i] += G;
         mem[destDataPos + i + 2] += G;
        }
      }
      break;
     case VMSF_AUDIO:
      {
       int dataSize = R[4], channels = R[0];
       int srcPos = 0;
       int destDataPos = dataSize;
       // byte *SrcData=Mem,*DestData=SrcData+DataSize;
       setValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
       if (dataSize >= VM_GLOBALMEMADDR / 2)
        {
         break;
        }
       for (int curChannel = 0; curChannel < channels; curChannel++)
        {
         long prevByte = 0;
         long prevDelta = 0;
         long Dif[] = new long[7];
         int D1 = 0, D2 = 0, D3;
         int K1 = 0, K2 = 0, K3 = 0;

         for (int i = curChannel, byteCount = 0; i < dataSize; i += channels, byteCount++)
          {
           D3 = D2;
           D2 = (int) prevDelta - D1;
           D1 = (int) prevDelta;

           long predicted = 8 * prevByte + K1 * D1 + K2 * D2 + K3 * D3;
           predicted = (predicted >>> 3) & 0xff;

           long curByte = mem[srcPos++] & 0xff;

           predicted = (predicted - curByte) & UINT_MASK;
           mem[destDataPos + i] = (byte) predicted;
           prevDelta = (byte) (predicted - prevByte);
           prevByte = predicted;

           int D = ((byte) curByte) << 3;

           Dif[0] += Math.abs(D);
           Dif[1] += Math.abs(D - D1);
           Dif[2] += Math.abs(D + D1);
           Dif[3] += Math.abs(D - D2);
           Dif[4] += Math.abs(D + D2);
           Dif[5] += Math.abs(D - D3);
           Dif[6] += Math.abs(D + D3);

           if ((byteCount & 0x1f) == 0)
            {
             long minDif = Dif[0], numMinDif = 0;
             Dif[0] = 0;
             for (int j = 1; j < Dif.length; j++)
              {
               if (Dif[j] < minDif)
                {
                 minDif = Dif[j];
                 numMinDif = j;
                }
               Dif[j] = 0;
              }
             switch ((int) numMinDif)
              {
              case 1:
               if (K1 >= -16)
                K1--;
               break;
              case 2:
               if (K1 < 16)
                K1++;
               break;
              case 3:
               if (K2 >= -16)
                K2--;
               break;
              case 4:
               if (K2 < 16)
                K2++;
               break;
              case 5:
               if (K3 >= -16)
                K3--;
               break;
              case 6:
               if (K3 < 16)
                K3++;
               break;
              }
            }
          }
        }
      }
      break;
     case VMSF_UPCASE:
      {
       int dataSize = R[4], srcPos = 0, destPos = dataSize;
       if (dataSize >= VM_GLOBALMEMADDR / 2)
        {
         break;
        }
       while (srcPos < dataSize)
        {
         byte curByte = mem[srcPos++];
         if (curByte == 2 && (curByte = mem[srcPos++]) != 2)
          {
           curByte -= 32;
          }
         mem[destPos++] = curByte;
        }
       setValue(false, mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize);
       setValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
      }
      break;
     }

   }

  private void filterItanium_SetBits(int curPos, int bitField, int bitPos, int bitCount)
   {
    int inAddr = bitPos / 8;
    int inBit = bitPos & 7;
    int andMask = 0xffffffff >>> (32 - bitCount);
    andMask = ~(andMask << inBit);

    bitField <<= inBit;

    for (int i = 0; i < 4; i++)
     {
      mem[curPos + inAddr + i] &= andMask;
      mem[curPos + inAddr + i] |= bitField;
      andMask = (andMask >>> 8) | 0xff000000;
      bitField >>>= 8;
     }

   }

  private int filterItanium_GetBits(int curPos, int bitPos, int bitCount)
   {
    int inAddr = bitPos / 8;
    int inBit = bitPos & 7;
    int bitField = (int) (mem[curPos + inAddr++] & 0xff);
    bitField |= (int) ((mem[curPos + inAddr++] & 0xff) << 8);
    bitField |= (int) ((mem[curPos + inAddr++] & 0xff) << 16);
    bitField |= (int) ((mem[curPos + inAddr] & 0xff) << 24);
    bitField >>>= inBit;
    return (bitField & (0xffffffff >>> (32 - bitCount)));
   }

  public void setMemory(int pos, byte[] data, int offset, int dataSize)
   {
    if (pos < VM_MEMSIZE)
     { // && data!=Mem+Pos)
      // memmove(Mem+Pos,Data,Min(DataSize,VM_MEMSIZE-Pos));
      for (int i = 0; i < Math.min(data.length - offset, dataSize); i++)
       {
        if ((VM_MEMSIZE - pos) < i)
         {
          break;
         }
        mem[pos + i] = data[offset + i];
       }
     }
   }

 }

//